//
// Filter
//

#ifndef FILTER_H
#define FILTER_H

#include "structures.h"

// Min/Max
#define max(a,b) (((a) > (b)) ? (a) : (b))
#define min(a,b) (((a) < (b)) ? (a) : (b))

#define OneOver255 (0.003921569f) 
#define Point3Over255 (0.001176471f)
#define ThreeFiveFiveOver255 (1.392156995f)

#define EdgeTreshold (61)

// Filters row and writes the result to destRow,
// uses preRow and postRow for fetching neighbouring pixels. 
// Parameter y is the absolute row number with respect to the top of the image.
void filterRow(Row* preRow, Row* row, Row* postRow, Row* destRow, int y) {

	Pixel pixelLeft = row->pixels[IMAGE_WIDTH - 1];
	Pixel pixelCenter = row->pixels[0];
	Pixel pixelRight = row->pixels[1];

	int toLeftX = -50 - (IMAGE_WIDTH >> 1);
	int toCenterX = toLeftX + 50;
	int toRightX = toLeftX + 100;

	const int toCenterY = y - (IMAGE_HEIGHT >> 1);
	const int toBottomY = toCenterY + 75;
	
	const int sqToCenterY = toCenterY * toCenterY;
	const int sqToBottomY = toBottomY * toBottomY;

	int x;
	for (x = 0; x < IMAGE_WIDTH; x++) {

		// Determine vertically neighbouring pixels
		Pixel pixelUp = preRow->pixels[x];
		Pixel pixelDown = postRow->pixels[x];

		// Edge filter
		int edge = (pixelCenter.R + pixelCenter.G + pixelCenter.B) << 2;

		edge -= pixelLeft.R + pixelLeft.G + pixelLeft.B;
		edge -= pixelRight.R + pixelRight.G + pixelRight.B;
		edge -= pixelUp.R + pixelUp.G + pixelUp.B;
		edge -= pixelDown.R + pixelDown.G + pixelDown.B;

		// Filter
		float valr = sqrtf(toCenterX * toCenterX + sqToCenterY) * OneOver255;
		float valg = sqrtf(toLeftX * toLeftX + sqToBottomY) * Point3Over255;
		float valb = sqrtf(toRightX * toRightX + sqToBottomY) * Point3Over255;
		
		// Check if pixel is above edge treshold,
		// apply blur filter conditionally.
		float red, green, blue;

		if (edge <= EdgeTreshold) {

			// Blur filter
			red = 0.2f * (pixelLeft.R + pixelRight.R + pixelUp.R + pixelDown.R + pixelCenter.R);
			green = 0.2f * (pixelLeft.G + pixelRight.G + pixelUp.G + pixelDown.G + pixelCenter.G);
			blue = 0.2f * (pixelLeft.B + pixelRight.B + pixelUp.B + pixelDown.B + pixelCenter.B);

			// Linear interpolate between source pixel and blurred pixel
			red   = row->pixels[x].R * (1.0f - valr) + valr * red;
			green = row->pixels[x].G * (1.0f - valr) + valr * green;
			blue  = row->pixels[x].B * (1.0f - valr) + valr * blue;

		} else {

			red   = row->pixels[x].R;
			green = row->pixels[x].G;
			blue  = row->pixels[x].B;
		}

		// Scale and clamp to [0, 255]
		// Note: it is impossible that any of the values are below 0, 
		// this allows us to ommit the max(_, 0) check.
		int R = (1.3f + valr * 0.3f) * red;
		int G = valg * green + green;
		int B = (0.7f + valb) * blue;

		R = min(R, 255);
		G = min(G, 255);
		B = min(B, 255);

		// Scale based on distance to center,
		// write the result to destRow
		float scaleDistance = ThreeFiveFiveOver255 - valr; 

		// Note: distanceToCenter can't be higher then 200, so scaleDistance is always positive
		// this allows us to ommit a second max(_, 0) check.
		R = scaleDistance * R;
		G = scaleDistance * G;
		B = scaleDistance * B;

		destRow->pixels[x].R = (unsigned char) min(R, 255);
		destRow->pixels[x].G = (unsigned char) min(G, 255);
		destRow->pixels[x].B = (unsigned char) min(B, 255);

		// Shift pixels
		pixelLeft = pixelCenter;
		pixelCenter = pixelRight;
		pixelRight = row->pixels[(x + 2) % IMAGE_WIDTH];

		toLeftX++;
		toCenterX++;
		toRightX++;
	}
}

#endif
